﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace Ganaye.EmbeddedDb
{
    public abstract class ClusteredFile
    {
        private Int64 mRecordCount;
        private int mRecordSize = 0;
        private int mHeaderSize = 0;
        internal ModifiedRecords mModifiedRecords;

        internal UInt32 mCurrentRecordNoPosition;
        private Dictionary<UInt32, LinkedListNode<Record>> mRecordsByRecordNo;
        private LinkedList<Record> mRecordsByLastUsed;
        private string mPath;
        protected Stream mOriginalStream;
        protected Stream mStream;

        protected abstract Record CreateNewProxyRecord();
        protected abstract void OnInitializeNewFile();
        protected abstract void OnReadHeader();
        private bool mIsNew;
        private ManualResetEvent mThreadHasExited;
        
        public ClusteredFile()
        {
            mRecordsByLastUsed = new LinkedList<Record>();
            mRecordsByRecordNo = new Dictionary<UInt32, LinkedListNode<Record>>();
            mModifiedRecords = new SlowModifiedRecordsUsingLockAndSortedDictionary();
            mThreadHasExited = new ManualResetEvent(true);
        }


        internal void AddRecord(Record record)
        {
#if ASSERTS
            System.Diagnostics.Debug.Assert(record.mTable.mFile == this, "Invalid file.");
#endif

            if (record.RecordNo == uint.MaxValue)
            {
                throw new RuntimeException("Internal Error: Invalid RecordNo");
            }
           
            bool shouldStartThread = false;
            
            mModifiedRecords.Set(record.RecordNo, record, ref shouldStartThread);
            if (shouldStartThread)
            {
                mThreadHasExited.Reset();
                ThreadPool.QueueUserWorkItem(new WaitCallback(BackgroundWorker));

            }
        }



        private void BackgroundWorker(object state)
        {
            uint recordNo = uint.MaxValue;
            Record record = null;
#if ASSERTS
                Trace.WriteLine(string.Format("New thread #{0}: {1} records to save.", ThreadNo, mModifiedRecords.Count));
#endif
            while (true) // we can't check the modifiedRecords out of "lock"
            {
                mModifiedRecords.RemoveAndGetNext(recordNo, ref recordNo, ref record);
                if (recordNo == uint.MaxValue) break;
                InternalWriteRecord(record);
            }
            mThreadHasExited.Set();
        }

        public bool IsNew
        {
            get { return mIsNew; }
        }

        public void Open(string path, System.IO.Stream originalStream)
        {
            mPath = path;
            mOriginalStream = originalStream;
            if (originalStream is BufferedStream)
                mStream = originalStream;
            else
                mStream = new BufferedStream(originalStream);
            mIsNew = (mStream.Length == 0);

            if (mIsNew)
                OnInitializeNewFile();
            else
                OnReadHeader();
        }

        private void Seek(UInt32 newRecordNo)
        {
#if ASSERTS
            Debug.Assert(newRecordNo < UInt32.MaxValue, "Invalid RecordNo");
#endif
            if (newRecordNo == mCurrentRecordNoPosition) return;
            mStream.Seek(newRecordNo * mRecordSize + mHeaderSize, SeekOrigin.Begin);
            mCurrentRecordNoPosition = newRecordNo;
        }

        internal UInt32 RecordCount
        {
            get
            {
                return (UInt32)mRecordCount;
            }
        }

        internal uint GetNewRecordNo()
        {
            // we don't want to record that have the same number
            // interlocked is thread safe
            var result = Interlocked.Increment(ref mRecordCount) - 1; ;
            // and interestingly we want the recordNo (that is count-1)
            return (UInt32)result;
        }

        internal Int32 RecordSize
        {
            get
            {
                return mRecordSize;
            }
        }

        internal Int32 HeaderSize
        {
            get
            {
                return mHeaderSize;
            }
        }

        internal virtual protected Record InternalGetRecord(UInt32 recordNo, bool returnNullIfNotInCache)
        {
#if ASSERTS
            Debug.Assert(recordNo < UInt32.MaxValue, "Invalid RecordNo");
#endif
            LinkedListNode<Record> linkListNode = null;
            Record record = null;

            if (mRecordsByRecordNo.TryGetValue(recordNo, out linkListNode))
            {
                record = linkListNode.Value;
                return record;
            }

            if (returnNullIfNotInCache) return null;
            // we're here because the record was disposed or never created

            Record newRecord = CreateNewProxyRecord();
            Seek(recordNo);
            mStream.Read(newRecord.GetReadBuffer(), 0, mRecordSize);
            mCurrentRecordNoPosition = recordNo + 1;
            return newRecord;
        }

        internal void InternalWriteRecord(Record record)
        {
            Byte[] bytes = record.InternalGetWriteBytes();
            UInt32 recordNo = record.RecordNo;
            Seek(recordNo);
            mStream.Write(bytes, 0, bytes.Length);
            mCurrentRecordNoPosition = recordNo + 1;
        }


        private void RemoveLastUsed(LinkedListNode<Record> linkListNode)
        {
        }

        private void SetLastUsed(LinkedListNode<Record> linkListNode)
        {
        }

        //internal Record InternalAppendNewRecord()
        //{
        //    Debug.Assert(mRecordSize > 0, "RecordSize should not be bigger than zero.");
        //    UInt32 recordNo = mRecordCount++;
        //    Record newRecord = CreateNewProxyRecord();
        //    Byte[] readBuffer = new Byte[mRecordSize];
        //    newRecord.mReadBuffer = readBuffer;
        //    return newRecord;
        //}

        protected internal void Initialize(int newHeaderSize, int newRecordSize)
        {
            mHeaderSize = newHeaderSize;
            mRecordSize = newRecordSize;
            if (mStream.Length >= mHeaderSize)
            {
                mRecordCount = (uint)((mStream.Length - mHeaderSize) / mRecordSize);
            }
            else
            {
                mRecordCount = 0;
            }
        }

        internal void Close()
        {
            mThreadHasExited.WaitOne();
            mStream.Close();
        }
    }
}

